﻿// -------------------------------------------------------------------------- //
//	文件名		: 	CAutoIt.cs
//	创建者		:	xianglinjian
//	创建时间	:	2010-10-20   18:13:32
//	功能描述	:   AutoIt工具的封装类
//
// -------------------------------------------------------------------------- //
#if ENABLE_EXCEL_COM
#define ENABLE_AutoItX
#elif ENABLE_ET_COM
#define ENABLE_AutoItX
#endif

#if ENABLE_AutoItX
namespace autoIt
{
    using System;
    using System.Timers;
    using System.Threading;
    using System.Collections.Generic;
    using AutoItX3Lib;

    public class CAutoIt
    {
        public delegate void FuncShellBlock(bool bUnknown);
        // 1、绝大多数情况下是阻止未知对话框；此时 bUnknown = false;
        // 2、如有一个ET或Excel进程60秒后CPU时间未变，则应该是COM服务器的问题，也算block，此时 bUnknown = true;
        protected FuncShellBlock func_ShellBlock;
        protected AutoItX3Lib.AutoItX3 m_au3;
        protected Thread m_autoItThread;
        protected bool m_bKeepRunning;
        protected bool m_bMonitorET;
        protected int m_nSecond = 0;
        protected WND_FLAG m_wndFlag;
        protected CHistory m_dlgHistory = new CHistory(10);
        protected Queue<CDlgInfo> m_ignoreWnd = new Queue<CDlgInfo>();
        protected static Dictionary<CDlgKey, List<CDlgValue>> s_ruleDic = new Dictionary<CDlgKey, List<CDlgValue>>();
        private const int WAITE_TIME = 1500;
        private int m_nCheckCount;
        protected CUnknownBlock m_unknownBlock;

        public CAutoIt()
        {
            m_ignoreWnd.Enqueue(new CDlgInfo("TfmMain.UnicodeClass", "WPS 表格 2009 - ", null, null));
            m_ignoreWnd.Enqueue(new CDlgInfo("wndclass_desked_gsk", "Microsoft Visual Basic -", null, null));
            m_ignoreWnd.Enqueue(new CDlgInfo("XLMAIN", "Microsoft Excel -", null, null));
            func_ShellBlock = DoBlockNothing;
            m_unknownBlock = new CUnknownBlock();
            m_nCheckCount = 0;
        }
        public void AddBlockFunc(FuncShellBlock func)
        {
            func_ShellBlock = func;
        }

        protected void DoBlockNothing(bool bUnknown)
        {
            tool.Output.DumpWriteLine("program is blocked!");
        }

        public static bool AddKillRule(string _class, string _title, string _text, string _action)
        {
            if (_text != null && _text.Length == 0)
            {
                _text = null;
            }
            CDlgInfo di = new CDlgInfo(_class, _title, _text, _action);
            return AddKillRule(di);
        }

        protected static bool AddKillRule(CDlgInfo di)
        {
            List<CDlgValue> valList;
            if (s_ruleDic.TryGetValue(di.dlgKey, out valList))
            {
                foreach (CDlgValue val in valList)
                {
                    if (di.dlgValue.Equals(val))
                    {
                        return false;
                    }
                }
                valList.Add(di.dlgValue);
            }
            else
            {
                valList = new List<CDlgValue>();
                valList.Add(di.dlgValue);
                s_ruleDic[di.dlgKey] = valList;
            }
            return true;
        }

        public void ResetWndInfo()
        {
            m_wndFlag = WND_FLAG.NONE;
        }
        public bool IsNeedPassword()
        {
            return (m_wndFlag == WND_FLAG.NO_PASSWORD);
        }

        // 整个工具的初始化
        public void Start()
        {
            m_au3 = new AutoItX3Lib.AutoItX3();
            m_au3.AutoItSetOption("WinTitleMatchMode", 4);

            m_autoItThread = new Thread(new ThreadStart(ThreadRun));
            m_autoItThread.Start();
        }

        public void Stop()
        {
            m_au3 = null;
            
        }

        public void BeginMonitor(bool bMonitorET)
        {
            m_bKeepRunning = true;
            m_bMonitorET = bMonitorET;
        }

        public void EndMonitor()
        {
            m_bKeepRunning = false;
            m_nCheckCount = 0;
            m_unknownBlock.Clear();
        }

        private void ThreadRun()
        {
            while (m_bKeepRunning)
            {
                CheckBlockingWnd();
                Console.Write(".");
                Thread.Sleep(WAITE_TIME);
                if(m_nCheckCount ++ < 25)   // 检查一次
                {
                    continue;
                }
                m_nCheckCount = 0;
                if (m_unknownBlock.IsUnknownBlocked(m_bMonitorET))
                {
                    func_ShellBlock(true);
                }
            }
        }

        protected void CheckBlockingWnd()
        {
            Console.Write(".");
            m_au3.AutoItSetOption("WinTitleMatchMode", 2);
            Object[,] wndList = (Object[,])m_au3.WinList("", "");
            m_au3.AutoItSetOption("WinTitleMatchMode", 4);
            for (int i = 1; i <= (int)wndList[0, 0]; ++i)
            {
                string sTitle = (string)wndList[0, i];
                string sHandle = (string)wndList[1, i];
                string wndHandle = String.Format("[HANDLE:{0:g}]", sHandle);
                
                // 是否是忽略的对话框
                if (IsIgnoreWnd(sHandle))
                {
                    continue;
                }

                // 是否是规则中的对话框
                if (DoMatchWnd())
                {
                    continue;
                }

                // 默认处理模拟回车
                m_au3.WinSetOnTop(wndHandle, "", 1);
                m_au3.Send("{Enter}", 0);

                if( 1 == m_au3.WinExists(wndHandle, "") )
                {
                    m_au3.WinSetOnTop(wndHandle, "", 0);
                }
                if(m_nSecond++ < 10)
                {
                    continue;
                }
                m_nSecond = 0;
                // 未知对话框，每十次检查才测试一次
                CDlgInfo di = GetDlgInfo(sHandle, wndHandle, sTitle);
                if (m_dlgHistory.IsBlockAdd(di))
                {
                    di.dlgValue.Action = "ignorefile";
                    AddKillRule(di);
                    AddKillRuleToConfig(di);
                    m_wndFlag = WND_FLAG.SHELL_BLOCK;
                    m_au3.WinClose(wndHandle, "");
                    func_ShellBlock(false);
                    if (1 == m_au3.WinExists(wndHandle, ""))
                    {
                        m_au3.WinSetOnTop(wndHandle, "", 0);
                    }
                }
            }
        }

        private bool DoMatchWnd()
        {
            string wndHandle = null;
            string sAction = null;
            if (IsWndExist(ref wndHandle, ref sAction))
            {
                if (sAction == "password")
                {
                    m_wndFlag = WND_FLAG.NO_PASSWORD;
                    sAction = "!{f4}";
                }
                else if (sAction == "ignorefile")
                {
                    m_wndFlag = WND_FLAG.SHELL_BLOCK;
                    m_au3.WinClose(wndHandle, "");
                    func_ShellBlock(false);
                    return true;
                }
                else if(sAction == "crash")
                {
                    m_wndFlag = WND_FLAG.CRASH;
                    m_au3.WinClose(wndHandle, "");
                    func_ShellBlock(false);
                    return true;
                }
                
                m_au3.WinSetOnTop(wndHandle, "", 0);
                m_au3.WinActivate(wndHandle, "");
                m_au3.Send(sAction, 0);
                
                return true;
            }
            return false;
        }

        protected bool IsWndExist(ref string wndHangle, ref string sAction)
        {
            List<CDlgValue> valList = null;
            foreach (CDlgKey key in s_ruleDic.Keys)
            {
                if (1 == m_au3.WinExists(key.GetFormatKey(), ""))
                {
                    wndHangle = m_au3.WinGetHandle(key.GetFormatKey(), "");
                    wndHangle = String.Format("[HANDLE:{0:g}]", wndHangle);
                    valList = s_ruleDic[key];
                    break;
                }
            }
            // 规则中没有对应的对话框
            if (valList == null)
            {
                return false;
            }
            // 只对应一个
            if (valList.Count == 1)
            {
                sAction = valList[0].Action;
                return true;
            }

            // 包含文本优先
            string wndText = m_au3.WinGetText(wndHangle, "");
            if (wndText.Length > 20)
            {
                wndText = wndText.Substring(0, 20);
            }

            foreach (CDlgValue val in valList)
            {
                if (val.Text != null && wndText.Contains(val.Text))
                {
                    sAction = val.Action;
                    return true;
                }
            }

            foreach (CDlgValue val in valList)
            {
                if (val.Text == null)
                {
                    sAction = val.Action;
                    return true;
                }
            }
            return false;
        }

        // 忽略顶层窗口是cmd,ET,Excel,VB
        protected bool IsIgnoreWnd(string sHandle)
        {
            string sClassName = GetClassNameByHandle(sHandle);
            if(sClassName.Length == 0 || sClassName == "ConsoleWindowClass")
            {
                return true;
            }

            foreach (CDlgInfo di in m_ignoreWnd)
            {
                string sDescrib = String.Format("[Class:{0:g}; REGEXPTITLE:{1:g}*]",
                    di.dlgKey.ClassName, di.dlgKey.Title);
                string wndHandle = m_au3.WinGetHandle(sDescrib, "");
                if (wndHandle.Length > 0 && sHandle.Equals(wndHandle))
                {
                    return true;
                }
            }

            return false;
        }

        [System.Runtime.InteropServices.DllImport("user32.dll")]
        protected static extern int GetClassName(IntPtr hWnd, System.Text.StringBuilder lpString, int nMaxCount);
        protected string GetClassNameByHandle(string sHandle)
        {
            IntPtr nHandle = new IntPtr(Int32.Parse(sHandle, System.Globalization.NumberStyles.HexNumber));
            System.Text.StringBuilder className = new System.Text.StringBuilder(256);
            GetClassName(nHandle, className, className.Capacity);
            return className.ToString();
        }

        protected CDlgInfo GetDlgInfo(string sHandle, string wndHandle, string sTitle)
        {
            CDlgInfo di = new CDlgInfo();
            di.dlgKey.ClassName = GetClassNameByHandle(sHandle);
            if (wndHandle == null)
            {
                wndHandle = String.Format("[HANDLE:{0:g}]", sHandle);
            }

            if (sTitle == null)
            {
                di.dlgKey.Title = m_au3.WinGetTitle(wndHandle, "");
            }
            else
            {
                di.dlgKey.Title = sTitle;
            }

            string wndText = m_au3.WinGetText(wndHandle, "");
            if (wndText.Length > 20)
            {
                di.dlgValue.Text = wndText.Substring(0, 20);
            }
            else
            {
                di.dlgValue.Text = wndText;
            }

            return di;
        }

        protected void AddKillRuleToConfig(CDlgInfo di)
        {
            try
            {
                System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
                doc.Load(ET_Improve.Global.sConfigFile);
                System.Xml.XmlNode root = doc.SelectSingleNode("config/Global");
                System.Xml.XmlElement node = doc.CreateElement("KillRule");
                node.SetAttribute("DlgClass", di.dlgKey.ClassName);
                node.SetAttribute("DlgCaption", di.dlgKey.Title);
                node.SetAttribute("DlgText", di.dlgValue.Text);
                node.SetAttribute("Action", di.dlgValue.Action);

                root.AppendChild(node);
                doc.Save(ET_Improve.Global.sConfigFile);
            }
            catch (System.Exception ex)
            {
                tool.Output.DumpWriteLine(ex.ToString());
            }
        }

        protected enum WND_FLAG
        {
            NONE,
            NO_PASSWORD,
            SHELL_BLOCK,
            CRASH,
        }

        // className 和 title标识一组对话框
        protected class CDlgKey
        {
            public string ClassName { set; get; }
            public string Title { set; get; }
            public CDlgKey()
            {
            }
            public CDlgKey(string _className, string _title)
            {
                ClassName = _className;
                Title = _title;
            }

            public string GetFormatKey()
            {
                return String.Format("[CLASS:{0:g}; TITLE:{1:g}]", ClassName, Title);
            }

            public override int GetHashCode()
            {
                return base.GetHashCode();
            }

            public bool Equals(CDlgKey other)
            {
                if (ReferenceEquals(null, other))
                {
                    return false;
                }
                if (ReferenceEquals(this, other))
                {
                    return true;
                }

                return (other.ClassName == ClassName && other.Title == Title);
            }

            public override bool Equals(object obj)
            {
                if (ReferenceEquals(null, obj))
                {
                    return false;
                }
                if (ReferenceEquals(this, obj))
                {
                    return true;
                }
                if (obj.GetType() != typeof(CDlgKey))
                {
                    return false;
                }
                return Equals((CDlgKey)obj);
            }
        }

        protected class CDlgValue
        {
            public string Text { set; get; }
            public string Action { set; get; }
            public CDlgValue()
            {
            }
            public CDlgValue(string _txt, string _action)
            {
                Text = _txt;
                Action = _action;
            }

            public override int GetHashCode()
            {
                return base.GetHashCode();
            }

            public bool Equals(CDlgValue other)
            {
                if (ReferenceEquals(null, other))
                {
                    return false;
                }
                if (ReferenceEquals(this, other))
                {
                    return true;
                }
                
                return (other.Text == Text && other.Action == Action);
            }

            public override bool Equals(object obj)
            {
                if (ReferenceEquals(null, obj))
                {
                    return false;
                }
                if (ReferenceEquals(this, obj))
                {
                    return true;
                }
                if (obj.GetType() != typeof(CDlgValue))
                {
                    return false;
                }
                return Equals((CDlgValue)obj);
            }
        }

        protected class CDlgInfo
        {
            public CDlgKey dlgKey;
            public CDlgValue dlgValue;
            public CDlgInfo()
            {
                dlgKey = new CDlgKey();
                dlgValue = new CDlgValue();
            }
            public CDlgInfo(CDlgKey _key, CDlgValue _val)
            {
                dlgKey = _key;
                dlgValue = _val;
            }

            public CDlgInfo(string _class, string _title, string _text, string _action)
            {
                dlgKey = new CDlgKey(_class, _title);
                dlgValue = new CDlgValue(_text, _action);
            }

            public override int GetHashCode()
            {
                return base.GetHashCode();
            }

            public bool Equals(CDlgInfo other)
            {
                if (ReferenceEquals(null, other))
                {
                    return false;
                }
                if (ReferenceEquals(this, other))
                {
                    return true;
                }

                return other.dlgKey.Equals(dlgKey) && other.dlgValue.Equals(dlgValue);
            }

            public override bool Equals(object obj)
            {
                if (ReferenceEquals(null, obj))
                {
                    return false;
                }
                if (ReferenceEquals(this, obj))
                {
                    return true;
                }
                if (obj.GetType() != typeof(CDlgInfo))
                {
                    return false;
                }
                return Equals((CDlgInfo)obj);
            }
        }

        protected class CHistory
        {
            private int m_capacity;
            private int m_size = 0;
            private Queue<CDlgInfo> m_data;
            public CHistory(int _cap)
            {
                m_capacity = _cap;
                m_data = new Queue<CDlgInfo>(m_capacity);
            }

            public bool IsBlockAdd(CDlgInfo val)
            {
                if (m_data.Count == m_capacity)
                {
                    m_data.Dequeue();
                    m_size = m_capacity;
                    if (IsAllSame(val))
                    {
                        return true;
                    }
                }
                else
                {
                    ++m_size;
                }
                m_data.Enqueue(val);
                return false;
            }

            protected bool IsAllSame(CDlgInfo di)
            {
                foreach (CDlgInfo val in m_data)
                {
                    if (!di.Equals(val))
                    {
                        return false;
                    }
                }

                return true;
            }
        }

        protected class CUnknownBlock
        {
            protected Dictionary<int, int> m_cputime;
            public CUnknownBlock()
            {
                m_cputime = new Dictionary<int, int>();
            }

            public void Clear()
            {
                m_cputime.Clear();
            }

            public bool IsUnknownBlocked(bool bMonitorET)
            {
                Dictionary<int, int> temp = new Dictionary<int, int>();
                if (bMonitorET)
                {
                    DoProcess("ET", temp);
                }
                else
                {
                    DoProcess("EXCEL", temp);
                }
                
                // 与原数据进行比较
                if(temp.Count == 0)
                {
                    m_cputime.Clear();
                    return false;
                }

                if (m_cputime.Count == 0)
                {
                    m_cputime = temp;
                    return false;
                }

                List<int> kill = new List<int>();
                foreach (int pid in temp.Keys)
                {
                    if(!m_cputime.ContainsKey(pid))
                    {
                        continue;
                    }

                    if(temp[pid] == m_cputime[pid])
                    {
                        kill.Add(pid);
                    }
                }
                if (kill.Count > 0)
                {
                    KillProcess(kill);
                    return true;
                }
                m_cputime.Clear();
                m_cputime = temp;
                return false;
            }

            protected void KillProcess(List<int> kill)
            {
                try
                {
                    foreach (int pid in kill)
                    {
                        System.Diagnostics.Process p = System.Diagnostics.Process.GetProcessById(pid);
                        p.Kill();
                    }
                }
                catch (System.Exception ex)
                {
                    Console.WriteLine("KillProcess Exception" + ex.Message);
                }
            }

            protected void DoProcess(string sProcessName, Dictionary<int, int> dic)
            {
                System.Diagnostics.Process[] processes =
                        System.Diagnostics.Process.GetProcessesByName(sProcessName);
                foreach (System.Diagnostics.Process p in processes)
                {
                    SetProcessTm(p, dic);
                }
            }

            // 考虑到同步的问题
            protected void SetProcessTm(System.Diagnostics.Process p, Dictionary<int, int> dic)
            {
                try
                {
                    dic.Add(p.Id, p.TotalProcessorTime.Seconds);
                }
                catch (System.Exception ex)
                {
                    Console.WriteLine("SetProcessTm exception:" + ex.Message);
                }
            }
        }
    }
}
#endif // #if ENABLE_AutoItX